/*
 * Copyright 2021 ABSA Group Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package za.co.absa.spline.harvester.dispatcher

import org.apache.spark.internal.Logging
import za.co.absa.spline.HierarchicalObjectFactory
import za.co.absa.spline.commons.config.ConfigurationImplicits._
import za.co.absa.spline.harvester.dispatcher.CompositeLineageDispatcher._
import za.co.absa.spline.producer.model.{ExecutionEvent, ExecutionPlan}

import scala.util.control.NonFatal

class CompositeLineageDispatcher(val delegatees: Seq[LineageDispatcher], failOnErrors: Boolean)
  extends LineageDispatcher
    with Logging {

  def this(objectFactory: HierarchicalObjectFactory) = this(
    objectFactory.createComponentsByKey(DispatchersKey),
    objectFactory.configuration.getRequiredBoolean(FailOnErrorsKey)
  )

  override def name: String = delegatees.map(_.name) mkString ", "

  override def send(plan: ExecutionPlan): Unit = delegate {
    _.send(plan)
  }

  override def send(event: ExecutionEvent): Unit = delegate {
    _.send(event)
  }

  private def delegate(call: LineageDispatcher => Unit): Unit = {
    delegatees.foreach(withErrorHandling(call))
  }

  private def withErrorHandling(call: LineageDispatcher => Unit): LineageDispatcher => Unit = { disp =>
    try call(disp)
    catch {
      case NonFatal(e) =>
        if (failOnErrors) throw e
        else logWarning(s"Proceeding after an error occurred in an underlying dispatcher: ${disp.getClass.getName}", e)
    }
  }
}

object CompositeLineageDispatcher {

  private val DispatchersKey = "dispatchers"
  private val FailOnErrorsKey = "failOnErrors"
}
